﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using Keyki.NefClass.Kernel.Data;
using Keyki.NefClass.Kernel.Fuzzy;
using Keyki.NefClass.Kernel.Fuzzy.ChooceRule;
using Keyki.NefClass.Kernel.Fuzzy.FuzzySet;
using Keyki.NefClass.Kernel.Fuzzy.Rulles;
using Keyki.NefClass.Kernel.Fuzzy.Trainer;
using Keyki.NefClass.Kernel.Reader;

namespace Keyki.NefClass.Kernel.Network
{
	public class NefClass
	{
		private DataSet dataSet;
		private List<FuzzyRule> rules;
		private AbstractRule ChooseAbstractRule;
		private ITrainer trainer;
		private readonly int rullesCount;

		public NefClass(AbstractRule ChooseAbstractRule, ITrainer trainer, int count)
		{
			rules = new List<FuzzyRule>();
			dataSet = new DataSet();
			this.ChooseAbstractRule = ChooseAbstractRule;
			this.trainer = trainer;
			rullesCount = count;
		}//AbstractNefClass

		public bool IsAny
		{
			get;
			set;
		}

		/// <summary>
		/// Percent of all sample that use for train
		/// </summary>
		public double TraineeSample { get; set; }

		/// <summary>
		/// Train fuzzySet params
		/// </summary>
		public void Train()
		{
			trainer.Train(dataSet, rules);
		}

		public void Work()
		{
			int count = dataSet.GetAt(0).Count;
			DataSample dataRow = null;
			Feature feature = null;

			for (int i = 0; i < count; i++)
			{
				//get dataRow from sample
				dataRow = dataSet.DataRow(i);

				var values = rules.Select(rule => new
				                                  	{
				                                  		Rule = rule,
				                                  		Activation = rule.Compute(dataRow)
				                                  	}).OrderByDescending(
				                                  		x => x.Activation);

				var bestRule = values.ElementAtOrDefault(0).Rule.Class;

				//Console.WriteLine("row : {0}, resultClass: {1} , mustBe: {2}, error: {3}", i, bestRule, dataSet[i], dataSet[i] - bestRule);

			}//for i

		}//Work

		public void CreateRulles()
		{
			PrepareLV();

			List<Antecedent> antecedents = new List<Antecedent>();

			int count = Convert.ToInt32(dataSet.GetAt(0).Count * TraineeSample);
			int max_i = -1;
			double max = double.MinValue;

			Feature feature = null;
			DataSample dataRow = null;
			Antecedent antecedent = null;

			for (int i = 0; i < count; i++)
			{
				//get dataRow from sample
				dataRow = dataSet.DataRow(i);
				antecedent = new Antecedent();

				for (int j = 0; j < dataRow.Count; j++)
				{
					feature = dataSet.GetAt(j);

					for (int k = 0; k < feature.CountLinguisticVariable; k++)
					{
						//go throw all Linguistic Variable for this feature to find one with maximum value
						var value = feature.GetAt(k).FuzzySet.Compute(dataRow[j]);
						if (max < value)
						{
							max = value;
							max_i = k;
						}
					}//for k
					antecedent[j] = feature.GetAt(max_i);

					max = double.MinValue;
					max_i = -1;
				}//for j

				CheckBeforeAdd(antecedents, antecedent);
			}//for i

			ChooseAbstractRule.Procedure(antecedents, dataSet, ref rules, rullesCount);
		}//CreateRulles

		/// <summary>
		/// Sets name for specify lingustic variable in feature
		/// </summary>
		/// <param name="featureId">id of feature</param>
		/// <param name="lvId">id of lingustic variable</param>
		/// <param name="name">name of new Lingustic variables</param>
		public void SetName(int featureId, int lvId, string  name)
		{
			if (featureId >= dataSet.Count)
				throw new IndexOutOfRangeException("No such Feature");

			var feature = dataSet.GetAt(featureId);

			if ( lvId >= feature.CountLinguisticVariable)
				throw new IndexOutOfRangeException("No such Lingustic variables");

			feature.GetAt(lvId).Name = name;
		}//SetName

		/// <summary>
		/// Creating dummy Lingustic variables for each feature in specified number 
		/// </summary>
		/// <param name="count">specified number of Lingustic variables</param>
		/// <param name="fuzzySet">fuzzySet for Lingustic variables</param>
		public void SetAllLV(int count, AbstractFuzzySet fuzzySet)
		{
			int fCount = dataSet.Count;

			for (int i = 0; i < fCount; i++)
			{
				var feature = dataSet.GetAt(i);
				for (int j = 0; j < count; j++)
				{
					var LV = new LinguisticVariable(j.ToString(), fuzzySet.Clone());
					feature.AddLinguisticVariable(LV);
				}//for j
			}//for i

			if (IsAny)
				AddAnyLV(fuzzySet);
		}//SetLV
		
		public void Init(string fileName, IReader reader)
		{
			reader.Parse(fileName, dataSet);
		}//Init()

		public string GetClassName(int id)
		{
			return dataSet.GetName(id);
		}//GetClassName()

		public void GetRullesInStream(StreamWriter stream)
		{
			rules.Sort(FuzzyRule.CompareRuleByClass);
			
			foreach (FuzzyRule rule in rules)
			{
				stream.WriteLine(rule.ToString());
			}
			stream.Close();
		}

		/// <summary>
		/// Prepare Lingustic variables 
		/// </summary>
		private void PrepareLV()
		{
			Feature feature = null;
			IEnumerable<double> collection = null;
			int featureCount = dataSet.Count;
			int count, part;

			for (int i = 0; i < featureCount; i++)
			{
				feature = dataSet.GetAt(i).Sort();
				count = feature.Count;
				part = IsAny ? feature.CountLinguisticVariable - 1 : feature.CountLinguisticVariable;

				//preparing data samples for LV
				int inOnePart = (int)Math.Ceiling((double)count / part);
				if (count % part == 0)
				{
					for (int j = 0; j < part; j++)
					{
						collection = feature.GetRange(j * inOnePart, inOnePart);
						var lv = feature.GetAt(j);
						lv.High = collection.Max();
						lv.Low = collection.Min();
						lv.FuzzySet.Init(collection);
					} //for i
				} //if
				else
				{
					for (int j = 0; j < part; j++)
					{
						collection = feature.GetRange(j * inOnePart - j, inOnePart);
						var lv = feature.GetAt(j);
						lv.High = collection.Max();
						lv.Low = collection.Min();
						lv.FuzzySet.Init(collection);
					} //for i
				} //else
			} //for i
		}//PrepareLV

		/// <summary>
		/// Add Lingustic variables with value "any" to all feature
		/// <param name="fuzzySet">fuzzySet for Lingustic variables</param>
		/// </summary>
		private void AddAnyLV(AbstractFuzzySet fuzzySet)
		{
			int fCount = dataSet.Count;

			for (int i = 0; i < fCount; i++)
			{
				fuzzySet = fuzzySet.Clone();
				var feature = dataSet.GetAt(i);
				var collection = feature.GetRange(0, feature.Count);
				fuzzySet.Init(collection);
				var lv = new LinguisticVariable("Any", fuzzySet);
				lv.High = collection.Max();
				lv.Low = collection.Min();
				feature.AddLinguisticVariable(lv);
			}//for i
		}//AddAnyLV

		private void CheckBeforeAdd(List<Antecedent> antecedents, Antecedent antecedent)
		{
			if (!antecedents.Contains(antecedent))
			{
				antecedents.Add(antecedent);
			}//if
		}//CheckBeforeAdd
	}//AbstractNefClass
}//Keyki.NefClass.Kernel.Network